package com.chensi.admin.userservice.service.impl;

import com.chensi.admin.userservice.common.BaseErrorCode;
import com.chensi.admin.userservice.common.Constants;
import com.chensi.admin.userservice.common.JsonResult;
import com.chensi.admin.userservice.dao.UserInfoRepository;
import com.chensi.admin.userservice.domain.UserInfo;
import com.chensi.admin.userservice.dto.UserInfoDTO;
import com.chensi.admin.userservice.dto.mapper.UserInfoMapper;
import com.chensi.admin.userservice.exception.BaseException;
import com.chensi.admin.userservice.feign.CommonService;
import com.chensi.admin.userservice.service.UserInfoService;
import com.chensi.admin.userservice.utils.*;
import lombok.AllArgsConstructor;
import net.sf.json.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @author si.chen
 * @date 2019/6/12 14:56
 */
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
@AllArgsConstructor
public class UserInfoServiceImpl implements UserInfoService {

    private final RedisUtil redisUtil;

    private final UserInfoRepository userInfoRepository;

    private final UserInfoMapper userInfoMapper;

    private final CommonService commonService;

    @Override
    public UserInfo find(UserInfo userInfo) {
        Optional<UserInfo> optional = userInfoRepository.findOne(getSpecification(userInfo));
        return optional.orElse(null);
    }

    @Override
    public void checkPhone(String phone) throws BaseException {
        UserInfo param = new UserInfo();
        param.setPhone(phone);
        UserInfo user = this.find(param);
        if (user != null) {
            throw new BaseException(BaseErrorCode.PHONE_EXIST);
        }
    }

    @Override
    public void checkCode(String phone, String code) throws BaseException {
        Object realCode = redisUtil.get(phone);
        if (realCode == null || !StringUtils.equals(realCode.toString(), code)) {
            throw new BaseException(BaseErrorCode.CODE_ERROR);
        }
    }

    @Override
    public UserInfo findByPhone(String phone) {
        UserInfo param = new UserInfo();
        param.setPhone(phone);
        return this.find(param);
    }

    @Override
    public String login(String phone, String password) throws BaseException {
        if (this.findByPhone(phone) == null) {
            throw new BaseException(BaseErrorCode.PHONE_NOT_EXIST);
        }
        UserInfo user = this.findByPhoneAndPassword(phone, DigestUtils.md5Hex(password));
        if (user == null) {
            String key = "user:" + phone + ":errorCount";
            Object num = redisUtil.get(key);
            if (num == null) {
                redisUtil.set(key, 1, Constants.LOGIN_ERROR_TIME_SCOPE);
            } else if ((int) num < Constants.LOGIN_ERROR_LIMIT) {
                redisUtil.incr(key, 1);
            } else {
                throw new BaseException(BaseErrorCode.LOGIN_FAIL_TOO_MUCH);
            }
            throw new BaseException(BaseErrorCode.PASSWORD_ERROR);
        }
        //AES
        AES.setKey(Constants.SALT);
        AES.encrypt(user.getId());
        return AES.getEncryptedString();

    }

    @Override
    @SuppressWarnings("Duplicates")
    public String loginByAli(String aliId) throws BaseException {
        UserInfo param = new UserInfo();
        param.setAliId(aliId);
        UserInfo entity = this.find(param);
        if (entity == null) {
            //未注册，跳回到注册手机号
            throw new BaseException(BaseErrorCode.AlI_WX_NOT_EXIST);
        }
        //check手机号注册
        if (StringUtils.isBlank(entity.getPhone())) {
            throw new BaseException(BaseErrorCode.PHONE_NOT_BIND);
        }
        //AES
        AES.setKey(Constants.SALT);
        AES.encrypt(entity.getId());
        return AES.getEncryptedString();
    }

    @Override
    @SuppressWarnings("Duplicates")
    public String loginByWx(String wxId) throws BaseException {
        UserInfo param = new UserInfo();
        param.setWxId(wxId);
        UserInfo entity = this.find(param);
        if (entity == null) {
            //未授权
            throw new BaseException(BaseErrorCode.AlI_WX_NOT_EXIST);
        }
        //check手机号注册
        if (StringUtils.isBlank(entity.getPhone())) {
            throw new BaseException(BaseErrorCode.PHONE_NOT_BIND);
        }
        //AES
        AES.setKey(Constants.SALT);
        AES.encrypt(entity.getId());
        return AES.getEncryptedString();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(UserInfo userInfo) throws BaseException {
        this.checkPassword(userInfo.getPassword());
        this.checkUniqueForCreate(userInfo);
        userInfo.setPassword(DigestUtils.md5Hex(userInfo.getPassword()));
        userInfo.setRealNameStatus(Constants.REAL_NAME_STATUS_NO);
        userInfoRepository.save(userInfo);
    }

    @Override
    public void createByAliOrWx(UserInfo userInfo) throws BaseException {
        this.checkUniqueForCreate(userInfo);
        userInfo.setRealNameStatus(Constants.REAL_NAME_STATUS_NO);
        userInfoRepository.save(userInfo);
    }

    @Override
    public UserInfo findByPhoneAndPassword(String phone, String password) {
        UserInfo param = new UserInfo();
        param.setPhone(phone);
        param.setPassword(password);
        return this.find(param);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserInfo update(UserInfo userInfo) throws BaseException {
        UserInfo entity = this.get(userInfo.getId());
        this.checkUniqueForUpdate(entity);
        BeanUtils.copyNotNullProperties(userInfo, entity);
        return userInfoRepository.save(entity);
    }

    @Override
    public UserInfo get(String id) throws BaseException {
        Optional<UserInfo> optional = userInfoRepository.findById(id);
        return optional.orElseThrow(() -> new BaseException(BaseErrorCode.USER_NOT_EXIST));
    }

    @Override
    public UserInfoDTO detail(String id) throws BaseException {
        UserInfoDTO dto = userInfoMapper.toDTO(this.get(id));
        AES.setKey(Constants.SALT);
        AES.decrypt(id);
        dto.setId(AES.getDecryptedString());
        return dto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String id) throws BaseException {
        UserInfo entity = this.get(id);
        entity.setDeleted(Constants.DELETED_YES);
        userInfoRepository.save(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void forgotPassword(UserInfo userInfo) throws BaseException {
        UserInfo entity = this.findByPhone(userInfo.getPhone());
        if (entity == null) {
            throw new BaseException(BaseErrorCode.PHONE_NOT_EXIST);
        }
        if (StringUtils.isBlank(entity.getPassword())) {
            throw new BaseException(BaseErrorCode.PASSWORD_BLANK);
        }
        this.checkPassword(userInfo.getPassword());
        entity.setPassword(DigestUtils.md5Hex(userInfo.getPassword()));
        this.update(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetPassword(UserInfo userInfo) throws BaseException {
        UserInfo entity = this.get(userInfo.getId());
        if (StringUtils.isBlank(entity.getPassword())) {
            throw new BaseException(BaseErrorCode.PASSWORD_BLANK);
        }
        //检验旧密码
        entity = this.findByPhoneAndPassword(entity.getPhone(), DigestUtils.md5Hex(userInfo.getOldPassword()));
        if (entity == null) {
            throw new BaseException(BaseErrorCode.OLD_PASSWORD_ERROR);
        }
        this.checkPassword(userInfo.getPassword());
        entity.setPassword(DigestUtils.md5Hex(userInfo.getPassword()));
        this.update(entity);
    }

    @Override
    public void authenticateByPhone(UserInfo userInfo) throws BaseException {
        ResponseEntity<JsonResult> res = commonService.authenticateByPhone(userInfo);
        Object obj = ValidationUtil.validResponse(res);
        JSONObject result = JSONObject.fromObject(obj).getJSONObject(Constants.REAL_NAME_RESULT);
        if (StringUtils.equals(Constants.REAL_NAME_YES, (String) result.get(Constants.REAL_NAME_RES))) {
            //完成认证更新用户信息
            userInfo = this.findByPhone(userInfo.getPhone());
            userInfo.setRealNameType(Constants.REAL_NAME_PHONE);
            userInfo.setRealNameStatus(Constants.REAL_NAME_STATUS_YES);
            userInfo.setRealName((String) result.get(Constants.REAL_NAME_REALNAME));
            userInfo.setProvince((String) result.get(Constants.REAL_NAME_PROVINCE));
            userInfo.setCity((String) result.get(Constants.REAL_NAME_CITY));
            userInfo.setIdCardNum((String) result.get(Constants.REAL_NAME_IDCARD));
            this.update(userInfo);
        } else {
            throw new BaseException(BaseErrorCode.REAL_NAME_ERROR);
        }
    }

    private void checkPassword(String password) throws BaseException {
        if (!ValidationUtil.isPassword(password)) {
            throw new BaseException(BaseErrorCode.PHONE_FORMAT_ERROR);
        }
    }

    private List<UserInfo> list(UserInfo userInfo) {
        Sort sort = new Sort(Sort.Direction.ASC, "createTime");
        return userInfoRepository.findAll(this.getSpecification(userInfo), sort);
    }

    @SuppressWarnings("Duplicates")
    private void checkUniqueForCreate(UserInfo entity) throws BaseException {
        if (StringUtils.isNotBlank(entity.getPhone())) {
            UserInfo param = new UserInfo();
            param.setPhone(entity.getPhone().trim());
            if (CommonUtil.isNotEmptyList(this.list(param))) {
                throw new BaseException(BaseErrorCode.PHONE_EXIST);
            }
        }
    }

    @SuppressWarnings("Duplicates")
    private void checkUniqueForUpdate(UserInfo entity) throws BaseException {
        if (StringUtils.isNotBlank(entity.getPhone())) {
            UserInfo param = new UserInfo();
            param.setPhone(entity.getPhone().trim());
            param.setIdNe(entity.getId().trim());
            if (CommonUtil.isNotEmptyList(this.list(param))) {
                throw new BaseException(BaseErrorCode.PHONE_EXIST);
            }
        }
    }

    @SuppressWarnings("Duplicates")
    private Specification<UserInfo> getSpecification(UserInfo entity) {
        return (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            if (StringUtils.isNotBlank(entity.getPhone())) {
                Predicate predicate = cb.equal(root.get("phone"), entity.getPhone().trim());
                list.add(predicate);
            }
            if (StringUtils.isNotBlank(entity.getPassword())) {
                Predicate predicate = cb.equal(root.get("password"), entity.getPassword().trim());
                list.add(predicate);
            }
            if (StringUtils.isNotBlank(entity.getAliId())) {
                Predicate predicate = cb.equal(root.get("aliId"), entity.getAliId().trim());
                list.add(predicate);
            }
            if (StringUtils.isNotBlank(entity.getWxId())) {
                Predicate predicate = cb.equal(root.get("wxId"), entity.getWxId().trim());
                list.add(predicate);
            }
            if (StringUtils.isNotBlank(entity.getIdNe())) {
                Predicate predicate = cb.notEqual(root.get("id"), entity.getIdNe().trim());
                list.add(predicate);
            }
            query.where(list.toArray(new Predicate[0]));
            return query.getRestriction();
        };
    }

}
